// package org.apache.el.parser;
package com.gdevelop.gwtxp.rebind.el;


import com.gdevelop.gwtxp.client.GwtXP;
import com.gdevelop.gwtxp.rebind.GwtXPGenerator;

import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.i18n.client.Constants;
import com.google.gwt.user.client.ui.ImageBundle;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class ELParserContext {
  public static JMethod getMethod(JClassType type, String methodName, JType[] params) throws NotFoundException {
    try{
      return type.getMethod(methodName, params);
    }catch(NotFoundException e){
      // Try to getMethod on Superclass
      try{
        if (type.getSuperclass() != null){
          return getMethod(type.getSuperclass(), methodName, params);
        }
      }catch (NotFoundException e2){
        e2.getMessage();
      }

      // Try to getMethod on ImplementedInterfaces
      for (JClassType superInterface : type.getImplementedInterfaces()){
        try{
          return getMethod(superInterface, methodName, params);
        }catch (NotFoundException e2){
          e2.getMessage();
        }
      }
      throw new NotFoundException("Not found " + methodName + " in " + type.getQualifiedSourceName());
    }
  }

  private Compiler compiler;
  private GwtXPGenerator gwtXPGenerator;
  private TreeLogger logger;
  private GeneratorContext context;
  private JClassType pageContextType;
  private TypeOracle typeOracle;
  
  private TypeIntrospector introspector;
  private Map<String, JClassType> localBeans = new HashMap<String, JClassType>();

  /**
   * Common used types
   */
  protected JClassType TYPE_Constants;
  protected JClassType TYPE_ImageBundle;
  protected JClassType TYPE_GwtEvent;
  protected JClassType TYPE_GwtXP;

  protected JClassType TYPE_String;
  protected JClassType TYPE_Double;
  protected JClassType TYPE_Float;
  protected JClassType TYPE_Long;
  protected JClassType TYPE_Integer;
  protected JClassType TYPE_Short;
  protected JClassType TYPE_Char;
  protected JClassType TYPE_Byte;
  protected JClassType TYPE_Boolean;
  protected JClassType TYPE_Date;

  public ELParserContext(GwtXPGenerator gwtXPGenerator, String baseClassName) throws NotFoundException {
    this.gwtXPGenerator = gwtXPGenerator;
    this.logger = gwtXPGenerator.getLogger();
    this.context = gwtXPGenerator.getGeneratorContext();
    this.typeOracle = context.getTypeOracle();
    
    pageContextType = context.getTypeOracle().getType(baseClassName);

    TYPE_Constants = context.getTypeOracle().getType(Constants.class.getName());
    TYPE_ImageBundle = context.getTypeOracle().getType(ImageBundle.class.getName());
    TYPE_GwtEvent = context.getTypeOracle().getType(GwtEvent.class.getName());
    TYPE_GwtXP = context.getTypeOracle().getType(GwtXP.class.getName());

    TYPE_String = context.getTypeOracle().getType(String.class.getName());
    TYPE_Double = context.getTypeOracle().getType(Double.class.getName());
    TYPE_Float = context.getTypeOracle().getType(Float.class.getName());
    TYPE_Long = context.getTypeOracle().getType(Long.class.getName());
    TYPE_Integer = context.getTypeOracle().getType(Integer.class.getName());
    TYPE_Short = context.getTypeOracle().getType(Short.class.getName());
    TYPE_Char = context.getTypeOracle().getType(Character.class.getName());
    TYPE_Byte = context.getTypeOracle().getType(Byte.class.getName());
    TYPE_Boolean = context.getTypeOracle().getType(Boolean.class.getName());
    TYPE_Date = context.getTypeOracle().getType(Date.class.getName());
    
    introspector = new TypeIntrospector(context);
  }
  
  public void addObservableBean(String beanName){
    gwtXPGenerator.addObservableBean(beanName);
  }
  
  public Compiler _getCompiler(){
    return compiler;
  }
  public GeneratorContext getGeneratorContext(){
    return context;
  }
  public TypeOracle getTypeOracle(){
    return typeOracle;
  }
  public JClassType getPageContextType(){
    return this.pageContextType;
  }

  /**
   * Auto-boxing premilitary types to Object type
   */
  public JClassType autoboxed(JType type) throws NotFoundException {
    return autoboxed(context.getTypeOracle(), type);
  }
  public static JClassType autoboxed(TypeOracle typeOracle, JType type) throws NotFoundException {
    JPrimitiveType primitiveType = type.isPrimitive();
    if (type.isPrimitive() != null){
      if (primitiveType == JPrimitiveType.FLOAT)
        return typeOracle.getType(Float.class.getName());
      if (primitiveType == JPrimitiveType.DOUBLE)
        return typeOracle.getType(Double.class.getName());
      if (primitiveType == JPrimitiveType.LONG)
        return typeOracle.getType(Long.class.getName());
      if (primitiveType == JPrimitiveType.INT)
        return typeOracle.getType(Integer.class.getName());
      if (primitiveType == JPrimitiveType.SHORT)
        return typeOracle.getType(Short.class.getName());
      if (primitiveType == JPrimitiveType.BYTE)
        return typeOracle.getType(Byte.class.getName());
      if (primitiveType == JPrimitiveType.CHAR)
        return typeOracle.getType(Character.class.getName());
      if (primitiveType == JPrimitiveType.BOOLEAN)
        return typeOracle.getType(Boolean.class.getName());
    }
    return (JClassType)type;
  }
  
  public static String getGetMethodName(String propName){
    return "get" + propName.substring(0,1).toUpperCase() + propName.substring(1);
  }
  public static String getSetMethodName(String propName){
    return "set" + propName.substring(0,1).toUpperCase() + propName.substring(1);
  }

  public void addBean(String beanName, JClassType beanType){
    localBeans.put(beanName, beanType);
  }
  public void removeBean(String beanName){
    localBeans.remove(beanName);
  }
  public JClassType getLocalBeanType(String beanName){
    return localBeans.get(beanName);
  }

  public boolean isGWTSpecialClasses(JClassType type) throws NotFoundException {
    return (TYPE_Constants.isAssignableFrom(type))
      || (TYPE_ImageBundle.isAssignableFrom(type))
    ;
  }
  
  public boolean isNumberType(JClassType type){
    return (TYPE_Double.isAssignableFrom(type))
      || (TYPE_Float.isAssignableFrom(type))
      || (TYPE_Long.isAssignableFrom(type))
      || (TYPE_Integer.isAssignableFrom(type))
      || (TYPE_Short.isAssignableFrom(type))
      || (TYPE_Char.isAssignableFrom(type))
      || (TYPE_Byte.isAssignableFrom(type))
    ;
  }
  public boolean isDateType(JClassType type){
    return (TYPE_Date.isAssignableFrom(type));
  }

  public JClassType getPropertyType(JClassType beanClass, String propName) throws NotFoundException {
    TypeIntrospector.PropertyDescriptor propDescriptor = introspector.getPropertyDescriptor(beanClass, propName);
    if (propDescriptor != null){
      return propDescriptor.getType();
    }
    
    throw new NotFoundException("Class " + beanClass.getQualifiedSourceName() + " does not have property " + propName);
  }
}
